package com.tyros.common.secure.password;

import com.tyros.common.secure.CryptoException;
import com.tyros.common.secure.codec.Codec;
import com.tyros.common.secure.utils.RandomKit;

import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;

/**
 * PBKDF2加密算法,支持导出256位密钥，导出数据结构为:<br> $base64(salt)$base64(ciphertext)
 *
 * @author zsy
 */
@SuppressWarnings("ALL")
public class PBKDF2PasswordEncoder implements PasswordEncoder {

    private static final String ALGORITHM = "PBKDF2WithHmacSHA256";

    private static final String SEPARATOR = "$";

    /**
     * 迭代次数
     */
    private int iterations = 1000;

    /**
     * 密文长度
     */
    private int keyLength = 256;

    private Codec codec = Codec.BASE64;


    public PBKDF2PasswordEncoder() {
    }

    public PBKDF2PasswordEncoder(int iterations, int keyLength) {
        this();
        this.iterations = iterations;
        this.keyLength = keyLength;
    }

    public PBKDF2PasswordEncoder(int iterations, int keyLength, Codec codec) {
        this();
        this.iterations = iterations;
        this.keyLength = keyLength;
        this.codec = codec;
    }

    private byte[] random() {
        return RandomKit.random(16);
    }

    @Override
    public String encode(String rawPassword) {
        return encode(rawPassword, random());
    }

    @Override
    public String encode(String rawPassword, byte[] salt) {
        if (salt == null) {
            throw new CryptoException("salt cannot be empty");
        }
        if (rawPassword == null || rawPassword.length() <= 0) {
            throw new CryptoException("password cannot be empty");
        }
        PBEKeySpec spec = new PBEKeySpec(rawPassword.toCharArray(), salt, iterations, keyLength);
        SecretKeyFactory skf;
        try {
            skf = SecretKeyFactory.getInstance(ALGORITHM);
            byte[] encoded = skf.generateSecret(spec).getEncoded();
            return SEPARATOR + codec.encode(salt) + SEPARATOR + codec.encode(encoded);
        } catch (Exception e) {
            throw new CryptoException(e);
        }
    }

    @Override
    public boolean matches(String rawPassword, String encodedPassword) {
        String[] split = encodedPassword.split("\\" + SEPARATOR);
        if (split.length != 3) {
            throw new CryptoException("password is invalid");
        }
        try {
            byte[] salt = codec.decode(split[1]);
            byte[] srcEncoded = codec.decode(split[2]);
            PBEKeySpec spec = new PBEKeySpec(rawPassword.toString().toCharArray(), salt, iterations, keyLength);
            byte[] destEncoded = SecretKeyFactory.getInstance(ALGORITHM).generateSecret(spec).getEncoded();
            return byteMatches(srcEncoded, destEncoded);
        } catch (Exception e) {
            throw new CryptoException(e);
        }
    }

    @Override
    public String[] split(String cipherText) {
        //todo
        return new String[2];
    }

    @Override
    public String merge(String cipherHeader, String cipherPayload) {
        //todo
        return null;
    }

    public static boolean byteMatches(byte[] src, byte[] dest) {
        if (src == null || dest == null || src.length != dest.length) {
            return false;
        }
        byte result = 0;
        int len = src.length;
        for (int i = 0; i < len; i++) {
            result |= src[i] ^ dest[i];
        }
        return result == 0;
    }
}
